/* 
The License

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
 */

package com.photobucket.webapi.objects
{
	import com.photobucket.webapi.events.AlbumEvent;
	import com.photobucket.webapi.factory.AlbumFactory;
	import com.photobucket.webapi.factory.MediaFactory;
	import com.photobucket.webapi.interfaces.IAlbum;
	import com.photobucket.webapi.interfaces.IMedia;
	import com.photobucket.webapi.interfaces.IService;
	import com.photobucket.webapi.oauth.IOAuthRequest;
	import com.photobucket.webapi.oauth.OAuthRequest;
	import com.photobucket.webapi.oauth.OAuthRequestMethod;
	import com.photobucket.webapi.oauth.urlEncode;
	
	import flash.events.Event;
	import flash.net.FileReference;
	
	import mx.collections.ArrayCollection;
	import mx.collections.Sort;
	import mx.collections.SortField;
	import mx.utils.ObjectUtil;
	
	/**
	 * Represents a album on photobucket and all the associated actions that can be taken
	 * @author jlewark
	 * 
	 */
	public class Album extends PhotobucketRemoteObject implements IAlbum
	{
		
		protected var _albumURL:String;

		protected var _privacy:String;
		protected var _photo_count:int = 0;
		protected var _subalbum_count:int = 0;
		protected var _video_count:int = 0;
		protected var _name:String;
		protected var _mediaTypes:Object = new Object();
		protected var subAlbums:ArrayCollection;
		protected var subAlbumsLoaded:Boolean = false;
		protected var _parent:IAlbum;
		protected var invalid:Boolean = false;
		protected var _location:String;
		protected var _sortOrder:String;
		
		/**
		 * Constructor:  Optionally takes the URL and/or the data repesenation of the XML 
		 * @param albumURL
		 * @param owner
		 * 
		 */
		public function Album(albumURL:String = null, data:Object = null, service:IService = null) {
			super(service);
			this.setURL(albumURL);
			this.data = data;
			dispatchEvent(new Event("urlUpdated"));
		}

		[Bindable(event="urlUpdated")]
		/**
		 * Returns the URL that can be used to access the album 
		 * @return 
		 * 
		 */
		public function get url():String {
			return _albumURL.replace(/\/$/, "");			
		}

		[Bindable(event="complete")]
		/**
		 * Returns the name of the album 
		 * @return 
		 * 
		 */
		public function get name():String {
			return _name;
		}
		
		/**
		 * Returns the display name for the album 
		 * @return 
		 * 
		 */
		public function get displayName():String {
			if (depth == 0) {
			   return _name + "'s default album";	
			}
			return name;
		}
		
		public function get listName():String {
			var spacing:String = "";
			var depth:int = this.depth;
			for (var i:int = 1; i < depth; i++) {
				spacing += "...";
			}
			return spacing + name;
		}
		
		[Bindable(event="complete")]
		/**
		 * Gets the photo_count in the current album if it has been retrived by the api 
		 * @return 
		 * 
		 */		
		public function get photo_count():int {
			return _photo_count;
		}

		[Bindable(event="complete")]		
		/**
		 * gets the sub album count of the current ablum if it has been retrived by the api 
		 * @return 
		 * 
		 */
		public function get subalbum_count():int {
			return _subalbum_count;
		}
		
		[Bindable(event="complete")]
		/**
		 * gets the video count of the current album if it has been retrived by the api 
		 * @return 
		 * 
		 */
		public function get video_count():int {
			return _video_count;
		}
		
		protected function setURL(albumURL:String):void {
			_albumURL = unescape(albumURL);
			server = _albumURL;
			id = _albumURL;
			_name = unescape(id).replace(/\/$/, "").match(/[^\/]+$/)[0];
			if (_name != "") {
				var cleanId:String = unescape(id);
				var aray:Array = cleanId.split("/");
				aray.shift();
				_location = aray.join("/");
			} else {
				_location = "";				
			}
			_owner = unescape(id).split("/", 1)[0];
		}

		/**
		 * Sets the id of the album based on the url or directly. The ID is the 
		 * username + path of the album
		 * @param value
		 * 
		 */		
		override public function set id(value:String):void {
			try {
				if (value.indexOf("http") >= 0) {
					var pathParse:RegExp = /^([^\/]*\/){5}(?P<path>[^?]+[^\/\?]($)?)((\/[^\/]+\.[a-zA-Z]{3,4}(\?[^\?]*$|$))|\/$|\?$|\/\?|$)+/;
					var values:Array = pathParse.exec(value);
					super.id = values.path;
				} else {
					super.id = value;
				}
			} catch (error:Error) {
				var i:int = 0;
				i++;
				i++;
			}
		}
		
		[Bindable(event="subAlbumsUpdated")]	
		/**
		 * Returns an Array Collection of the current album's subalbums
		 * @return 
		 * 
		 */
		public function get sub_albums():ArrayCollection {
			if (subAlbumsLoaded == false) {
				var request:IOAuthRequest = createRequest();
				request.url = server+"/album/" + id;
				request.needsLogin = true;
				request.addParameter("media", "none");
				request.result = function (value:Object):void {
					var results:XML = value as XML;
					loadSubAlbums(results);
				}
				request.fault = this.fault
				pbservice.makeRequest(request);				
			} 
			return subAlbums;
		}
		
		public function get sortOrder():String {
			return _sortOrder;
		}
		
		
		/**
		 * Gets the media of the secific type in an array collection.  This collection will populate as the result is returned.  It will
		 * trigger the COLLECTION_CHANGED event as items are added.   We can also populate the sub album list if its not there already because
		 * we get that info anyway.
		 * @param type
		 * @return 
		 * 
		 */		
		public function getMedia(type:String = "image"):ArrayCollection {
			if (_mediaTypes.hasOwnProperty(type)) {
				return getCachedMediaByType(type);
			} else {
				var parentRef:IAlbum = this;
				var request:IOAuthRequest = createRequest();
				request.needsLogin = true;
				request.url = server+"/album/" + id;
				request.addParameter("media", type);
				request.result = function (value:Object):void {
					data = value;
					var results:XML = value as XML;
					for each (var mediaXML:XML in results..media) {
						var mediaObj:IMedia = MediaFactory.mediaFactory(null, mediaXML);
						mediaObj.album = parentRef;
						getCachedMediaByType(type).addItem(mediaObj);
					}
					if (subAlbumsLoaded == false) {
						loadSubAlbums(results);
					}
				}
				request.fault = this.fault
				pbservice.makeRequest(request);
			}
			return getCachedMediaByType(type);
		}
		
		public function isMediaLoaded():Boolean {
			return _mediaTypes.hasOwnProperty("all");
		}
		
		 override protected function fault(value:Object):void {
			var something:String = "bad happened";
		 	sendComplete();	
		}
		[Bindable(event="mediaChanged")]
		/**
		 * Returns all media for the album that is available though the API 
		 * @return 
		 * 
		 */
		public function get media():ArrayCollection {
			return this.getMedia("all");
		}
		
		
		/**
		 * Lasy function for returning just the images from an album 
		 * @see getMedia
		 * @return 
		 * 
		 */
		[Bindable(event="imageChanged")]
		public function get images():ArrayCollection {
			return this.getMedia("image");
		}
		
		/**
		 * Lasy function for return just the videos from an album
		 * @see getMedia
		 * @return 
		 * 
		 */		
		[Bindable(event="videoChanged")]
		public function get video():ArrayCollection {
			return this.getMedia("video");
		}
		

		[Bindable(event='privacyUpdated')]
		/**
		 * Returns the current privacy level of the album 
		 * @return 
		 * 
		 */		
		public function get privacy():String {
			if (_privacy == null) {
				var request:IOAuthRequest = createRequest();
				request.url = server+"/album/" + id +"/privacy";
				request.fault = this.fault;
				request.result = function (result:Object):void {
					_privacy = result.privacy.toString();
					dispatchEvent(new Event('privacyUpdated'));
				}
				pbservice.makeRequest(request);
			}
			return _privacy;
		}
		
		/**
		 * Set the album to public or private 
		 * @param value
		 * 
		 */		
		public function set privacy(value:String):void {
			if (_privacy != value) {
				if ((value == "private") || (value == "public")) {
					invalid = true;
					_privacy = value;
				}
			}
		}

		/**
		 * Creates a subalum in the current album with the name given.  It will add it to the subalbum collection
		 * if it has already been retrived 
		 * @param name
		 * 
		 */
		public function createSubAlbum(name:String):void {
			var request:IOAuthRequest = createRequest();
			request.method = OAuthRequestMethod.POST;
			request.needsLogin = true;
			request.addParameter("name", name);
			request.url = server+"/album/" + id;
			var currentAlbum:IAlbum = this;
			request.fault = function (fault:Object):void {
				dispatchEvent(new AlbumEvent(AlbumEvent.ALBUM_CREATE_ERROR, currentAlbum));
			}
			request.result = function (result:Object):void {
				if (subAlbumsLoaded) {
					var newAlbum:IAlbum = AlbumFactory.albumFactory(url + "/"+name);
					newAlbum.parent = currentAlbum;
					var index:int = -1;
					for (var i:int = 0; i < subAlbums.length; i++) {
						var compresult:int = ObjectUtil.stringCompare(subAlbums[i].name, newAlbum.name, true);
						if ( compresult == -1) {
							index = i;
						} else {
							break;
						}
					}
					subAlbums.addItemAt(newAlbum, index + 1);
					dispatchEvent(new AlbumEvent(AlbumEvent.ALBUM_CREATED, newAlbum));
					dispatchEvent(new Event("subAlbumCreated"));
				}
			}
			pbservice.makeRequest(request);
		}
		
		/**
		 * Renames the current album... all children of the album are now invalid as their links will
		 * no longer function.  It is recommed you re-retrive the album again.
		 * @param newName
		 * 
		 */
		public function rename(newName:String):void {
			var request:IOAuthRequest = createRequest();
			request.method = OAuthRequestMethod.PUT;
			request.needsLogin = true;
			request.addParameter("name", newName);
			request.url = server+"/album/" + id;
			request.fault = this.fault;
			request.result = function (result:Object):void {
				id = id.replace(name, newName);
				_albumURL = _albumURL.replace(name, newName);
				_name = newName;
				dispose();
			}
			pbservice.makeRequest(request);
		}
				
		/**
		 * Uploads a FileReference or File object up to the server.
		 * @param file
		 * @param type
		 * @param title
		 * @param description 
		 */
		public function uploadFile(file:FileReference, type:String = "image", title:String = null, description:String = null, index:int = 0):void {
			var request:IOAuthRequest = createRequest();
			request.url = server+"/album/" + id +"/upload";
			request.file = file;
			request.addParameter("type", type);
			if (title != null) {
				request.addParameter("title", title);
			} 
			if (description != null) {
				request.addParameter("description", description);
			}
			request.needsLogin = true;
			request.method = OAuthRequestMethod.POST;
			request.fault = this.fault;
			request.result = function (result:Object):void {
				var newMedia:IMedia = MediaFactory.mediaFactory(null, result);
				addMediaAt(newMedia, index);
			}
			pbservice.makeRequest(request);
		}
		
		/**
		 * Adds a media to the proper arrays can be used for various applications but primarily a hook to add
		 * a UploadableMedia object to inject itself.
		 * @param media
		 * @param index
		 * 
		 */
		public function addMediaAt(media:IMedia, index:int = 0):void {
			if (_mediaTypes.hasOwnProperty('all')) {
				getCachedMediaByType('all').addItemAt(media, index);
			}
			if (_mediaTypes.hasOwnProperty(media.type)) {
				getCachedMediaByType(media.type).addItemAt(media, 0);
			}
		}
		
		
		/**
		 * Disposes of the Album Object clearing any children that might sill have
		 * references from this object.  
		 * 
		 */
		public function dispose():void {
			for each (var album:IAlbum in subAlbums) {
				album.parent = null;
			}
			subAlbums = null;
			subAlbumsLoaded = false;
			for each (var p:Object in _mediaTypes) {
				for each (var media:IMedia in _mediaTypes[p]) {
					media.album = null;
				}
				ArrayCollection(p).removeAll();
			}
			_mediaTypes = null;
			
		}

		[Bindable(event="complete")]
		/**
		 * Returns a displayable path 
		 * @return 
		 * 
		 */
		public function get path():String {
			return unescape(id);
		}
		
		/**
		 * gets the pararent album of this album
		 * @return 
		 * 
		 */
		public function get parent():IAlbum {
			if (_parent == null) {
				if (isRootAlbum() == false) {
					var index:Number = url.lastIndexOf(name);
					var parentDir:String = this.url.slice(0, index -1);
					_parent = AlbumFactory.albumFactory(parentDir + "/");				
				}
			}
			return _parent;
		}
		/**
		 * sets the parent album of this abum 
		 * @param value
		 * 
		 */		
		public function set parent(value:IAlbum):void {
			_parent = value;
		}
		
		public function isRootAlbum():Boolean {
			if (_parent != null) {
				return false;
			}
			var index:Number = url.lastIndexOf(name);
			var parentDir:String = this.url.slice(0, index -1);
			trace(parentDir);
			var parts:Array = parentDir.split("/");
			trace(parts.length);
			if (parts.length > 5) {
				return false;
			} else {
				return true;
			}
		}
		
		public function get depth():int {
			var url:String = this._albumURL.replace(/\/$/, "");
			var matches:Array = url.match(/\//g);
			return matches.length - 5;
		}
		
		public function organize(sortOrder:String = "manual"):void {
			_sortOrder = sortOrder;
			var request:IOAuthRequest = createRequest();
			request.method = OAuthRequestMethod.POST;
			request.needsLogin = true;
			request.addParameter("order_type", sortOrder);
			if (sortOrder == "manual") {
				var orderString:String;
				var orderedArray:Array = new Array();
				for each (var mediaObj:IMedia in this.media) {
					orderedArray.push(mediaObj.name);
				}
				var sortString:String = orderedArray.join(",");
				request.addParameter("order", sortString);
			}
			request.fault = function(object:Object):void {
				trace(object.toString());
				//var sdfds:Alert = Alert.show(object.toString());
			}
			request.result = function(object:Object):void {
				//dispatchEvent(new Event(Event.COMPLETE));
			}
			request.url = server+"/album/" + id +"/organize";
			pbservice.makeRequest(request);
			
		}
		
		public function contains(item:Object):Boolean {
			if (_mediaTypes.hasOwnProperty('all')) {
				return getCachedMediaByType('all').contains(item);
			} else {
				return false;
			}
		}
		
		public function clear(media:IMedia):void {
			if (_mediaTypes.hasOwnProperty(media.type)) {
				if (getCachedMediaByType(media.type).contains(media)) {
					getCachedMediaByType(media.type).removeItemAt(getCachedMediaByType(media.type).getItemIndex(media));
				}
			}
			if (_mediaTypes.hasOwnProperty('all')) {
				if (getCachedMediaByType('all').contains(media)) {
					getCachedMediaByType('all').removeItemAt(getCachedMediaByType('all').getItemIndex(media));
				}
			}
		}
		

		public function commit():void {
			if (invalid) {
				var request:IOAuthRequest = createRequest();
				request.method = OAuthRequestMethod.PUT;
				request.needsLogin = true;
				request.addParameter("privacy", privacy);
				request.url = server+"/album/" + id +"/privacy";
				request.fault = this.fault;
				request.result = function (result:Object):void {
					_privacy = XML(result).privacy;
					dispatchEvent(new Event('privacyUpdated'));
				}
				pbservice.makeRequest(request);
			}			
		}
		
		
		public function setPrivacy(privacy:String, password:String = null):void {
			var request:IOAuthRequest = createRequest();
			request.method = OAuthRequestMethod.PUT;
			request.needsLogin = true;
			request.addParameter("privacy", privacy);
			if (password != null) {
				request.addParameter("password", password);
			}
			request.url = server+"/album/" + id +"/privacy";
			request.fault = this.fault;
			request.result = function (result:Object):void {
				_privacy = XML(result).privacy;
				dispatchEvent(new Event('privacyUpdated'));
			}
			pbservice.makeRequest(request);			
		}

		public function clearcache():void {
			var request:IOAuthRequest = createRequest();
			request.method = OAuthRequestMethod.PUT;
			request.needsLogin = true;
			request.url = server+"/album/" + id +"/completeupload";
			request.fault = this.fault;
			request.result = function (result:Object):void {
				_privacy = XML(result).privacy;
				dispatchEvent(new Event('clearcache'));
			}
			pbservice.makeRequest(request);			
		}		
		
		
		internal function getCachedMediaByType(type:String):ArrayCollection {
			if (_mediaTypes.hasOwnProperty(type) == false) {
				_mediaTypes[type] = new ArrayCollection();
				if (type == 'all') {
					dispatchEvent(new Event("mediaChanged"));
				} else {
					dispatchEvent(new Event(type + "Changed"));
				}
			}
			return this._mediaTypes[type];	
		}
		
		// TODO: can we share this with HTMLAlbum?
		private function loadSubAlbums(results:XML):void {
			subAlbums = new ArrayCollection();
			var parentRef:IAlbum = this;
			for each (var subAlbumXML:XML in results.album..album) {
				var newAlbum:IAlbum = AlbumFactory.albumFactory(url+"/"+subAlbumXML.@name, subAlbumXML);
				newAlbum.parent = parentRef;
				subAlbums.addItem(newAlbum);
			}
			subAlbumsLoaded = true;
			dispatchEvent(new Event("subAlbumsUpdated"));
			
		}
		
		override internal function set data(value:Object):void {
			if (value) {
				if (value.hasOwnProperty("@name")) {
					_name = value.@name;
				}
				if (value.hasOwnProperty("@photo_count")) {
					_photo_count = int(value.@photo_count); 
				} 
				if (value.hasOwnProperty("@subalbum_count")) {
					_subalbum_count = int(value.@subalbum_count); 
				}
				if (value.hasOwnProperty("@video_count")) {
					_video_count = int(value.@video_count); 
				}
				if (value.hasOwnProperty("@privacy")) {
					_privacy = value.@privacy;
					this.dispatchEvent(new Event("privacyUpdated"));
				}
				super.data = value;
			}
		}
		
		/**
		 * Add a child object.  This some other API calls might provided with with child media or albums and instead of making
		 * additional calls we want to pass this info along for performance sakes.
		 * @param child
		 * 
		 */
		public function addChild(child:Object):void {
			if (child is IAlbum) {
				if (subAlbums == null) {
					subAlbumsLoaded = true;
					subAlbums = new ArrayCollection();

				}	
				subAlbums.addItem(child);
			}
			if (child is IMedia) {
				throw new Error("Method not implemented yet");
			}
		}

		public function addChildAt(child:Object, index:int = 0):void {
			if (child is IAlbum) {
				if (subAlbums == null) {
					subAlbumsLoaded = true;
					subAlbums = new ArrayCollection();
					
				}	
				subAlbums.addItemAt(child, index);
			}
			if (child is IMedia) {
				throw new Error("Method not implemented yet");
			}
		}

		
		/**
		 * Get the path within the user's directory without domain and path to the user
		 * @return 
		 * 
		 */
		public function get location():String {
			return _location;
		}
		
		public function get subAlbumCount():int {
			if (subAlbums == null) {
				return 0;
			}
			return subAlbums.length;
		}

		public function resetMedia():void {
			for each (var type:ArrayCollection in this._mediaTypes) {
				type.removeAll();
			}
			_mediaTypes = new Object();
		}		
		
		public function move(newParent:IAlbum):void {
			var request:IOAuthRequest = createRequest();
			request.method = OAuthRequestMethod.PUT;
			request.needsLogin = true;
			request.addParameter("parent", unescape(newParent.id));
			request.url = server+"/album/" + this.id;
			// here to avoid scoping ambiguity
			request.fault = function (result:Object):void {
				//moveAlbumDataModel(newParent);
				dispatchEvent(new Event('albumMoveFailed'));

			}
			request.result = function (result:Object):void {
				moveAlbumDataModel(newParent);
				dispatchEvent(new Event('albumMoved'));
			}
			pbservice.makeRequest(request);
		}
		
		protected function moveAlbumDataModel(newParent:IAlbum):void {
			var albumParent:IAlbum = _parent;
			var albumMoved:IAlbum = this;
			if (albumParent != null) {
				trace("Moving Album " + name);
				trace("       From " + albumParent.name);
				if (albumParent.sub_albums.length > 0) {
					trace("       " + albumParent.name + " has children");
					if (albumParent.sub_albums.contains(albumMoved)) {
						trace("       " + name + " removed from " + albumParent.name);
						albumParent.sub_albums.removeItemAt(_parent.sub_albums.getItemIndex(albumMoved));
					}
				}
			}
			this.setURL(newParent.url + "/" + this.name);
			_parent = newParent;
			trace("       Adding to " + newParent.name);
			var index:int = -1;
			for (var i:int = 0; i < newParent.sub_albums.length; i++) {
				var result:int = ObjectUtil.stringCompare(newParent.sub_albums[i].name, this.name, true);
				if ( result == -1) {
					index = i;
				} else {
					break;
				}
			}
			newParent.addChildAt(albumMoved, index + 1);
		}
		
	}
}